﻿using System;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;

namespace Game.FlowField.Burst
{
    [BurstCompile]
    public struct WeightUpdatingJob : IJob
    {
        [ReadOnly]
        public int2 MapSize;
        [ReadOnly]
        public int2 TargetIndex2D;

        /// <summary>
        /// Input
        /// </summary>
        [ReadOnly]
        public NativeArray<bool> GridIsBlocks;
        /// <summary>
        /// Output
        /// </summary>
        public NativeArray<int> GridWeights;

        [BurstDiscard]
        public void WrapData(MapData mapData)
        {
            this.MapSize = new int2(mapData.Width, mapData.Height);
            this.TargetIndex2D = mapData.TargetInfo.Active ? mapData.TargetInfo.Index2D : new int2(-1, -1);
            this.GridIsBlocks = mapData.GridIsBlocks;
            this.GridWeights = mapData.GridWeights;
        }

        [BurstCompile]
        public void Execute()
        {
            if (!MapDataUtil.IsInsideMap(this.TargetIndex2D, this.MapSize))
                return;

            var hasCheckedMarks = new NativeArray<bool>(GridIsBlocks.Length, Allocator.Temp);
            var targetIndex = MapDataUtil.ConvertIndex2D2Index(this.TargetIndex2D, this.MapSize);
            hasCheckedMarks[targetIndex] = true;
            this.GridWeights[targetIndex] = 0;

            var invalidWeightInfo = new WeightInfo() {Index = 0, Weight = Int32.MaxValue};
            var weights = new FlexibleArray<WeightInfo>(4, Allocator.Temp, true, invalidWeightInfo);
            weights.Add(new WeightInfo() { Index = targetIndex, Weight = 0 , Index2D = MapDataUtil.ConvertIndex2Index2D(targetIndex, this.MapSize) });
            var weightsTmp = new FlexibleArray<WeightInfo>(4, Allocator.Temp, true, invalidWeightInfo);
            var weightComparer = new WeightComparer();

            var crossQuadPoint = new CrossQuadPoint(this.MapSize);
            var xQuadPoint = new XQuadPoint(this.MapSize);

            while (weights.Length > 0)
            {
                weightsTmp.Clear();
                weights.Array.Sort(weightComparer);
                var length = weights.Length;
                for (int i = 0; i < length; i++)
                {
                    var weightInfo = weights.Array[i];
                    crossQuadPoint.Override(weightInfo.Index2D, weightInfo.Index);

                    // 上
                    if (crossQuadPoint.ContainsIndexA && 
                        !hasCheckedMarks[crossQuadPoint.IndexA] && 
                        !this.GridIsBlocks[crossQuadPoint.IndexA])
                    {
                        var tmpWeightInfo = new WeightInfo
                        {
                            Index = crossQuadPoint.IndexA,
                            Index2D = crossQuadPoint.Index2DA,
                            Weight = weightInfo.Weight + MapData.WeightOne
                        };
                        weightsTmp.Add(tmpWeightInfo);
                        hasCheckedMarks[tmpWeightInfo.Index] = true;
                        this.GridWeights[tmpWeightInfo.Index] = tmpWeightInfo.Weight;
                    }

                    // 下
                    if (crossQuadPoint.ContainsIndexB &&
                        !hasCheckedMarks[crossQuadPoint.IndexB] &&
                        !this.GridIsBlocks[crossQuadPoint.IndexB])
                    {
                        var tmpWeightInfo = new WeightInfo
                        {
                            Index = crossQuadPoint.IndexB,
                            Index2D = crossQuadPoint.Index2DB,
                            Weight = weightInfo.Weight + MapData.WeightOne
                        };
                        weightsTmp.Add(tmpWeightInfo);
                        hasCheckedMarks[tmpWeightInfo.Index] = true;
                        this.GridWeights[tmpWeightInfo.Index] = tmpWeightInfo.Weight;
                    }

                    // 左
                    if (crossQuadPoint.ContainsIndexC &&
                        !hasCheckedMarks[crossQuadPoint.IndexC] &&
                        !this.GridIsBlocks[crossQuadPoint.IndexC])
                    {
                        var tmpWeightInfo = new WeightInfo
                        {
                            Index = crossQuadPoint.IndexC,
                            Index2D = crossQuadPoint.Index2DC,
                            Weight = weightInfo.Weight + MapData.WeightOne
                        };
                        weightsTmp.Add(tmpWeightInfo);
                        hasCheckedMarks[tmpWeightInfo.Index] = true;
                        this.GridWeights[tmpWeightInfo.Index] = tmpWeightInfo.Weight;
                    }

                    // 右
                    if (crossQuadPoint.ContainsIndexD &&
                        !hasCheckedMarks[crossQuadPoint.IndexD] &&
                        !this.GridIsBlocks[crossQuadPoint.IndexD])
                    {
                        var tmpWeightInfo = new WeightInfo
                        {
                            Index = crossQuadPoint.IndexD,
                            Index2D = crossQuadPoint.Index2DD,
                            Weight = weightInfo.Weight + MapData.WeightOne
                        };
                        weightsTmp.Add(tmpWeightInfo);
                        hasCheckedMarks[tmpWeightInfo.Index] = true;
                        this.GridWeights[tmpWeightInfo.Index] = tmpWeightInfo.Weight;
                    }
                }

                for (int i = 0; i < length; i++)
                {
                    var weightInfo = weights.Array[i];
                    xQuadPoint.Override(weightInfo.Index2D, weightInfo.Index);

                    // 左上
                    if (xQuadPoint.ContainsIndexA &&
                        !hasCheckedMarks[xQuadPoint.IndexA] &&
                        !this.GridIsBlocks[xQuadPoint.IndexA])
                    {
                        var tmpWeightInfo = new WeightInfo
                        {
                            Index = xQuadPoint.IndexA,
                            Index2D = xQuadPoint.Index2DA,
                            Weight = weightInfo.Weight + MapData.WeightSlant
                        };
                        weightsTmp.Add(tmpWeightInfo);
                        hasCheckedMarks[tmpWeightInfo.Index] = true;
                        this.GridWeights[tmpWeightInfo.Index] = tmpWeightInfo.Weight;
                    }

                    // 右上
                    if (xQuadPoint.ContainsIndexB &&
                        !hasCheckedMarks[xQuadPoint.IndexB] &&
                        !this.GridIsBlocks[xQuadPoint.IndexB])
                    {
                        var tmpWeightInfo = new WeightInfo
                        {
                            Index = xQuadPoint.IndexB,
                            Index2D = xQuadPoint.Index2DB,
                            Weight = weightInfo.Weight + MapData.WeightSlant
                        };
                        weightsTmp.Add(tmpWeightInfo);
                        hasCheckedMarks[tmpWeightInfo.Index] = true;
                        this.GridWeights[tmpWeightInfo.Index] = tmpWeightInfo.Weight;
                    }

                    // 左下
                    if (xQuadPoint.ContainsIndexC &&
                        !hasCheckedMarks[xQuadPoint.IndexC] &&
                        !this.GridIsBlocks[xQuadPoint.IndexC])
                    {
                        var tmpWeightInfo = new WeightInfo
                        {
                            Index = xQuadPoint.IndexC,
                            Index2D = xQuadPoint.Index2DC,
                            Weight = weightInfo.Weight + MapData.WeightSlant
                        };
                        weightsTmp.Add(tmpWeightInfo);
                        hasCheckedMarks[tmpWeightInfo.Index] = true;
                        this.GridWeights[tmpWeightInfo.Index] = tmpWeightInfo.Weight;
                    }

                    // 右下
                    if (xQuadPoint.ContainsIndexD &&
                        !hasCheckedMarks[xQuadPoint.IndexD] &&
                        !this.GridIsBlocks[xQuadPoint.IndexD])
                    {
                        var tmpWeightInfo = new WeightInfo
                        {
                            Index = xQuadPoint.IndexD,
                            Index2D = xQuadPoint.Index2DD,
                            Weight = weightInfo.Weight + MapData.WeightSlant
                        };
                        weightsTmp.Add(tmpWeightInfo);
                        hasCheckedMarks[tmpWeightInfo.Index] = true;
                        this.GridWeights[tmpWeightInfo.Index] = tmpWeightInfo.Weight;
                    }
                }

                var tmp = weights;
                weights = weightsTmp;
                weightsTmp = tmp;
            }

            weights.Dispose();
            weightsTmp.Dispose();
        }
        
        [BurstCompile]
        public struct WeightInfo
        {
            public int Index;
            public int Weight;
            public int2 Index2D;
        }

        [BurstCompile]
        public struct WeightComparer : IComparer<WeightInfo>
        {
            [BurstCompile]
            public int Compare(WeightInfo x, WeightInfo y)
            {
                return x.Weight.CompareTo(y.Weight);
            }
        }
    }
}